#ifndef FLOCKMANAGER_H
#define FLOCKMANAGER_H

#include "string"

#include "ngl/Transformation.h"
#include "ngl/TransformStack.h"
#include "ngl/Random.h"
#include "ngl/Obj.h"

#include "ShaderManager.h"
#include "Boid.h"
#include "Wall.h"
#include "Utilities.h"
#include "ObstacleManager.h"


/// @file FlockManager.h
/// @brief deals with boids, boids movements and drawing
/// @author Perseedoss Rajiv
/// @version 1.0
/// @date 28.03.11
/// Revision History :
/// Initial Version 28.03.11
/// @class FlockManager
/// @brief manages flock of boids at a high level and synchronise their every move

class FlockManager
{
public:
    /// @brief ctor
    /// @param[in] _enable2DTopView flag to determine if 2d top view is enabled
    FlockManager(const bool _enable2DTopView = false);

    /// @brief dtor
    ~FlockManager();

    /// @brief adds a new boid to the flock
    void addNewBoidToFlock();

    /// @brief initialise the flock, recreating the boids
    /// @param[in] _enable2DTopView flag to determine if 2d top view is enabled
    void reInitialise(const bool _enable2DTopView);

    /// @brief initialise the flock, recreating the boids, overloaded to specify the number of boids
    /// @param[in] _enable2DTopView flag to determine if 2d top view is enabled
    /// @param[in] _initialNoOfBoids number of boids to be recreated
    void reInitialise(const bool _enable2DTopView, const int _initialNoOfBoids);

    /// @brief returns the count of the boids in the flock
    inline int getNumberOfBoids() const { return m_boidList->size(); }

    /// @brief creates vbo for boids
    void loadGeometry();

    /// @brief draws the boids
    /// @param[in] _txStack the transform stack, used for pushing and popping opengl states
    /// @param[in,out] io_sman the shader manager, used for shading/rendering of the boids
    void draw
            (
                ngl::TransformStack _txStack,
                ShaderManager* io_sman
            );

    /// @brief draws the marker for the tend towards position
    /// @param[in] _txStack the transform stack, used for pushing and popping opengl states
    /// @param[in,out] io_sman the shader manager, used for shading/rendering of the marker
    /// @param[in] _shader the shader used for drawing
    void drawTendTowardTarget
            (
                ngl::TransformStack _txStack,
                ShaderManager* io_sman,
                const std::string _shader
            );

    /// @brief moves the flock and engage all the flocking rules and movements
    /// @param[in] _wall the wall object that gives access to the boundaries
    /// @param[in,out] io_obstacleManager the obstacle manager that holds the list of obstacles
    void moveFlock
            (
                const Wall* _wall,
                ObstacleManager* io_obstacleManager
            );

    /// @brief sets the cohesion weight
    /// @param[in] _w cohesion weight
    inline void setCohesionWeight(const float _w) { m_cohesionWeight = _w; }

    /// @brief returns the cohesion weight
    inline float getCohesionWeight() const { return m_cohesionWeight; }

    /// @brief sets the alignment weight
    /// @param[in] _w alignment weight
    inline void setAlignmentWeight(const float _w) { m_alignmentWeight = _w; }

    /// @brief returns the alignment weight
    inline float getAlignmentWeight() const { return m_alignmentWeight; }

    /// @brief sets the separation weight
    /// @param[in] _w separation weight
    inline void setSeparationWeight(const float _w) { m_separationWeight = _w; }

    /// @brief returns the separation weight
    inline float getSeparationWeight() const { return m_separationWeight; }

    /// @brief sets the obstacle avoidance weight
    /// @param[in] _w avoidance weight
    inline void setObstacleAvoidanceWeight(const float _w) { m_obstacleAvoidanceWeight = _w; }

    /// @brief returns the obstacle avoidance weight
    inline float getObstacleAvoidanceWeight() const { return m_obstacleAvoidanceWeight; }

    /// @brief sets the flag to determine if cohesion is enabled
    /// @param[in] _state the cohesion enabled state
    inline void setEnableCohesion(const bool _state) { m_enableCohesion = _state; }

    /// @brief sets the flag to determine if separation is enabled
    /// @param[in] _state the separation enabled state
    inline void setEnableSeparation(const bool _state) { m_enableSeparation = _state; }

    /// @brief sets the flag to determine if alignment is enabled
    /// @param[in] _state the alignment enabled state
    inline void setEnableAlignment(const bool _state) { m_enableAlignment = _state; }

    /// @brief sets the flag to determine if obstacle avoidance is enabled
    /// @param[in] _state the obstacle avoidance enabled state
    inline void setEnableObstacleAvoidance(const bool _state) { m_enableObstacleAvoidance = _state; }

    /// @brief returns whether cohesion is enabled
    inline bool getEnableCohesion() const { return m_enableCohesion; }

    /// @brief returns whether separation is enabled
    inline bool getEnableSeparation() const { return m_enableSeparation; }

    /// @brief returns whether alignemnt is enabled
    inline bool getEnableAlignment() const { return m_enableAlignment; }

    /// @brief returns whether obstacle avoidance is enabled
    inline bool getEnableObstacleAvoidance() const { return m_enableObstacleAvoidance; }

    /// @brief sets the external force to be applied to the whole flock
    /// @param[in] _f the external force to apply
    inline void setExternalForce(const ngl::Vector _f) { m_externalForce = _f; }

    /// @brief returns whether external force is enabled
    inline ngl::Vector getExternalForce() const { return m_externalForce; }

    /// @brief sets the tend toward position
    /// @param[in] _pos the tend toward position
    inline void setTendToward(const ngl::Vector _pos) { m_tendPosition = _pos; }

    /// @brief returns the tend position
    inline ngl::Vector getTendToward() const { return m_tendPosition; }

    /// @brief sets the tend toward weight
    /// @param[in] _weight tend toward weight
    inline void setTendTowardWeight(const float _weight) { m_tendTowardWeight = _weight; }

    /// @brief returns the tend toward weight
    inline float getTendTowardWeight() const { return m_tendTowardWeight; }

    /// @brief sets the external force weight
    /// @param[in] _weight external force weight
    inline void setExternalForceWeight(const float _weight) { m_externalForceWeight = _weight; }

    /// @brief returns the external force weight
    inline float getExternalForceWeight() const { return m_externalForceWeight; }

    /// @brief returns whether tend toward is enabled
    inline bool getEnableTendToward() const { return m_enableTendToward; }

    /// @brief sets whether tend toward is enabled
    /// @param[in] _state the flag to determine whether tend toward is enabled
    inline void setEnableTendToward(const bool _state) { m_enableTendToward = _state; }

    /// @brief returns whether external force is enabled
    inline bool getEnableExternalForce() const { return m_enableExternalForce; }

    /// @brief sets whether external force is enabled
    /// @param[in] _state the flag to determine whether external force is enabled
    inline void setEnableExternalForce(const bool _state) { m_enableExternalForce = _state; }

    /// @brief sets whether normalise velocities flag is enabled
    /// @param[in] _state the flag to determine whether normalise velocities is enabled
    inline void setNormaliseRules(const bool _state) { m_normaliseRules = _state; }

    /// @brief returns wherether normalise velocities flag is enabled
    inline bool getNormaliseRules() const { return m_normaliseRules; }

    /// @brief returns wherether automatic flock movement is enabled
    inline bool getEnableAutoFlock() const { return m_enableAutoFlock; }

    /// @brief toggles the state of the automatic flock movement flag
    inline void toggleEnableAutoFlock() { m_enableAutoFlock ^= true; }

    /// @brief returns the angle of pitch of the current boid in the neighbour tracing
    inline float getPitchOfCurrentBoid() { Boid* b = (*m_boidList)[m_boidUnderNeighbourScreening]; return b->getAngleOfPitch(); }

    /// @brief returns the angle of yaw of the current boid in the neighbour tracing
    inline float getYawOfCurrentBoid() { Boid* b = (*m_boidList)[m_boidUnderNeighbourScreening]; return b->getAngleOfYaw(); }

    /// @brief returns the separation distance
    inline float getSeparationDistance() const { return m_separationDistance; }

    /// @brief sets the minimum separation distance between boids
    /// @param[in] _d the minimum distance between boids
    inline void setSeparationDistance(const float _d) { m_separationDistance = _d; }

    /// @brief returns the flock neighbouring threshold distance
    inline float getNeighbouringDistance() const { return m_neighbouringDistance; }

    /// @brief sets the minimum neighbouring distance for boids to flock
    /// @param[in] _d the minimum distance for flocking boids
    inline void setNeighbouringDistance(const float _d) { m_neighbouringDistance = _d; }

    /// @brief returns whether field of view neighbouring policy is enabled
    inline bool getEnableFOVNeighbouring() const { return m_enableFOVNeighbouring; }

    /// @brief sets whether field of view neighbouring policy is enabled
    /// @param[in] _state the flag to determine whether the enablement of fov neighbouring
    inline void setEnableFOVNeighbouring(const bool _state) { m_enableFOVNeighbouring = _state; }

    /// @brief returns the fov neighbouring angle
    inline float getFOVNeighbouringAngle() const { return m_neighbouringFOVAngle; }

    /// @brief sets the fov neighbouring angle
    /// @param[in] _value the fov neighbouring angle
    inline void setFOVNeighbouringAngle(const float _value) { m_neighbouringFOVAngle = _value; }

    /// @brief sets whether trace neighbour is enabled
    /// @param[in] _state flag to indicate whether trace neighbour is enabled;
    inline void setEnableBoidNeighbourScreening(const bool _state) { m_enableBoidNeighbourScreening = _state; }

    /// @brief returns whether neighbour tracing is enabled
    inline bool getEnableBoidNeighbourScreening() const { return m_enableBoidNeighbourScreening; }

    /// @brief sets the id of the boid under neighbour tracing
    /// @param[in] _val the boid's id that will currently be used for neighbour tracing
    inline void setNeighbourUnderScreen(const int _val) { m_boidUnderNeighbourScreening = _val; }

    /// @brief returns the id of the boid that is currently traced for its neighbours
    inline int getNeighbourUnderScreen() const { return m_boidUnderNeighbourScreening; }

    /// @brief returns whether boid wireframe drawing is enabled
    inline bool getWireframeBoids() const { return m_wireframeBoids; }

    /// @brief sets whether the boids are to be drawn in wireframe
    /// @param[in] _state the flag that determines whether wireframe drawing is enabled
    inline void setWireframeBoids(const bool _state) { m_wireframeBoids = _state; }

    /// @brief returns whether the bounding sphere of the boid is to be drawn
    inline bool getDrawBoidBoundingSphere() const { return m_drawBoidBoundingSphere; }

    /// @brief sets whether the boids' bounding sphere are to be drawn
    /// @param[in] _state the flag that determines the bounding sphere is to be drawn
    inline void setDrawBoidBoundingSphere(const bool _state) { m_drawBoidBoundingSphere = _state; }

    /// @brief returns the boid's model
    inline std::string getBoidModel() const { return m_boidModel; }

    /// @brief sets the boid's model name
    /// @param[in] _name the model name
    inline void setBoidModel(const std::string _name) { m_boidModel = _name; }

    /// @brief returns the boid's global scale factor
    inline float getBoidScaleFactor() const { return m_boidScaleFactor; }

    /// @brief sets the boid's scale factor
    /// @param[in] _scale the global scale factor
    inline void setBoidScaleFactor(const float _scale) { m_boidScaleFactor = _scale; }

    /// @brief returns the shader used to draw the boid
    inline std::string getBoidShader() const { return m_boidShader; }

    /// @brief sets the boid's shader for rendering
    /// @param[in] _name the shader name to used
    inline void setBoidShader(const std::string _name) { m_boidShader = _name; }

    /// @brief returns the maximum speed of the flock
    inline float getMaxSpeed() const { return m_maxSpeed; }

    /// @brief sets the flock maximum speed
    /// @param[in] _speed the maximum speed
    inline void setMaxSpeed(const float _speed) { m_maxSpeed = _speed; }

    /// @brief returns the minimum speed of the flock
    inline float getMinSpeed() const { return m_minSpeed; }

    /// @brief sets the flock minimum speed
    /// @param[in] _speed the minimum speed
    inline void setMinSpeed(const float _speed) { m_minSpeed = _speed; }

    /// @brief returns the global boid's color
    inline ngl::Colour getBoidColor() const { return m_boidColor; }

    /// @brief sets the boid's initial creation colour
    /// @param[in] _col the initial creation color
    inline void setBoidColor(const ngl::Colour _col) { m_boidColor = _col; }

    /// @brief returns whether boid's random color flag is enabled
    inline bool getEnableRandomBoidColor() const { return m_enableRandomBoidColor; }

    /// @brief sets whether random colours will be asigned to the boids on creation
    /// @param[in] _state the flag that determine whether random colour is enabled
    inline void setEnableRandomBoidColor(const bool _state) { m_enableRandomBoidColor = _state; }

    /// @brief returns the initial number of boid
    inline int getInitialNumberOfBoids() const { return m_initialNumberOfBoids; }


private:
    /// @brief the boid identifier
    int m_nextBoidId;

    /// @brief random number generator instance
    ngl::Random* rng;

    /// @brief boid model
    std::string m_boidModel;

    /// @brief shader used to draw the boid
    std::string m_boidShader;

    /// @brief the global scale of the boids
    float m_boidScaleFactor;

    /// @brief flag to determine if boids are drawned in wireframe
    bool m_wireframeBoids;

    /// @brief flag to determine if the bounding sphere of the boids is drawned
    bool m_drawBoidBoundingSphere;

    /// @brief the colour of boids
    ngl::Colour m_boidColor;

    /// @brief flag to determine if random colours are asigned to boids at creation time
    bool m_enableRandomBoidColor;

    /// @brief initial number of boids in flock
    int m_initialNumberOfBoids;

    /// @brief list of boids in flock
    std::vector<Boid*>* m_boidList;

    /// @brief cohesion weight
    float m_cohesionWeight;

    /// @brief alignment weight
    float m_alignmentWeight;

    /// @brief separation weight
    float m_separationWeight;

    /// @brief obstacle avoidance weight
    float m_obstacleAvoidanceWeight;

    /// @brief tend toward weight
    float m_tendTowardWeight;

    /// @brief external force weight
    float m_externalForceWeight;

    /// @brief flag to determine if cohesion is enabled
    bool m_enableCohesion;

    /// @brief flag to determine if separation is enabled
    bool m_enableSeparation;

    /// @brief flag to determine if alignment is enabled
    bool m_enableAlignment;

    /// @brief flag to determine if obstacle avoidance is enabled
    bool m_enableObstacleAvoidance;

    /// @brief flag to determine if tend toward is enabled
    bool m_enableTendToward;

    /// @brief flag to determine if external force is enabled
    bool m_enableExternalForce;

    /// @brief position to tend toward to
    ngl::Vector m_tendPosition;

    /// @brief external force to apply to the flock
    ngl::Vector m_externalForce;

    /// @brief flag to determine if velocities are to be normalised
    bool m_normaliseRules;

    /// @brief flag to determine if we are tracing down neighbours
    bool m_enableBoidNeighbourScreening;

    /// @brief the id of the boid that is being traced
    int m_boidUnderNeighbourScreening;

    /// @brief the minimum separation distance to keep between neighbours
    float m_separationDistance;

    /// @brief the threshold distance for boids to form neighbours
    float m_neighbouringDistance;

    /// @brief the neighbouring field of view angle
    float m_neighbouringFOVAngle;

    /// @brief flag to toggle the neighbouring field of view policy
    bool m_enableFOVNeighbouring;

    /// @brief the max speed of the flock
    float m_maxSpeed;

    /// @brief the min speed of the flock
    float m_minSpeed;

    /// @brief flag to control automatic movement of the flock
    bool m_enableAutoFlock;

    /// @brief flag to determine if 2d top view mode is enabled
    bool m_enable2DTopView;


private:
    /// @brief create a boid and add it to the list
    void createBoid();

    /// @brief evaluate neighbours of boids and build the neighbour list
    void refreshNeighbours();

    /// @brief evaluate neighbours of a specific boid and build its neighbour list
    /// @param[in,out] io_centerBoid the center boid whose neighbours are being evaluated
    void refreshNeighboursForABoid(Boid* io_centerBoid);

    /// @brief return whether a boid is a neighbour of a center boid under consideration
    /// @param[in] _mainId the id of the center boid, who has the neighbour list
    /// @param[in] _testId the id of the boid that we are checking in the neighbour list
    bool isANeighbour
                    (
                        const int _mainId,
                        const int _testId
                    );

    /// @brief keep boids within wall bounds
    /// @param[in,out] io_boid the boid that we want to contain within wall bounds
    /// @param[in] _wall the wall object that give access to boundaries
    void keepBoidWithinBoundaries
            (
                Boid* io_boid,
                const Wall* _wall
            );

    /// @brief initialise the flock and boids creation
    void initialiseBoids();

    /// @brief delete the list of boids and free up memory
    void deleteBoids();

    /// @brief draw the complex bird model
    /// @param[in] _boid the boid that is being drawn
    /// @param[in] _txStack the transform stack, used for pushing and popping opengl states
    /// @param[in,out] io_sman the shader manager, used for shading/rendering of the boid
    void drawComplexModel
            (
                const Boid* _boid,
                ngl::TransformStack _txStack,
                ShaderManager* io_sman
            );

    /// @brief draw basic primitive models
    /// @param[in] _boid the boid that is being drawn
    /// @param[in] _txStack the transform stack, used for pushing and popping opengl states
    /// @param[in,out] io_sman the shader manager, used for shading/rendering of the boid
    void drawPrimitiveModel
            (
                const Boid* _boid,
                ngl::TransformStack _txStack,
                ShaderManager* io_sman
            );

    /// @brief determine the colour to be applied to a boid and sets it to the shader
    /// @param[in] _boid the boid whose colour is set
    /// @param[in,out] io_sman the shader manager, used for shading/rendering of the boids
    void setBoidColorToShader
                        (
                            const Boid* _boid,
                            ShaderManager* io_sman
                        );


};

#endif // FLOCKMANAGER_H
